
import unittest

class TestEnumerate(unittest.TestCase):
    def test_enumerate(self):
        
        alist = ["cheka","kgb","stasi"]
        expected_dict = dict(zip(range(len(alist)),alist))
        
        adict = {}
        # enumerate returns a iterator, not a concrete list
        for index,element in enumerate(alist):
            adict[index] = element 
            
        self.assertEqual(expected_dict,adict)
        
    def test_override_iter(self):
        class TestContainer(object):
            def __init__(self,alist):
                self.__list = alist
                
            def __iter__(self):
                """
                when override '__iter__', it should return an enumerator, NOT an enumerator
                """
                return iter(self.__list)
            
        container = TestContainer(range(3))
        # as long as the custom object override "__iter__", then it can be iterable, then can be used in "for loop"
        # and can be used in some container's constructor
        elements = list(container)
        
        self.assertEqual([0,1,2],elements)
        
class TestGenerator(unittest.TestCase):
    """
    one big difference between generator and list comprehension is that generator is lazy
    """
    
    def test_generator_any(self):
        """
        generator expression is much like list comprehension
        but return an iterator, other than a concrete list which may occupies a lot of memory
        generator expression has much advantages in memory usage
        """
        orilist = [1,3,5,7,8]
        is_even_generator = ((x%2==0) for x in orilist)
        self.assertTrue(any(is_even_generator))
        
    def test_generator_all(self):
        """
        !!!!!!! pay much attention that even this testcase has only one line different from above one
        !!!!!!! but it cannot combine with the previous testcase and become a single testcase
        !!!!!!! the reason is that "Generator" cannot be re-used, which is much different from LINQ
        !!!!!!! Generator is a state-machine, after reaching its end, no more element can be generated out
        """
        orilist = [1,3,5,7,8]
        is_even_generator = ((x%2==0) for x in orilist)
        self.assertFalse(all(is_even_generator))
        
    def test_gen_expression_no_reused(self):
        generator = (x for x in xrange(3))
        
        first_list = []
        for x in generator:
            first_list.append(x)
        self.assertEqual(range(3),first_list)
        
        # same generator cannot be used again
        second_list = []
        for x in generator:
            second_list.append(x)
        self.assertEqual(0,len(second_list))
        
    def test_generator_never_reused(self):
        def simple_generator():
            yield 1
            yield "cheka"
            
        generator = simple_generator()
        
        # ------------- first time use that generator
        list1 = []
        for x in generator:
            list1.append(x)
            
        self.assertEqual([1,"cheka"],list1)
        
        # ------------- first time use that generator
        list2 = []
        for x in generator:
            list2.append(x)
            
        self.assertEqual(0,len(list2))
        
    def test_generator_with_next(self):
        def simple_generator():
            yield 1
            yield "cheka"
            
        generator = simple_generator();
        # when "next" is called, generator returns once, and pause to wait for next "next"
        self.assertEqual(1,generator.next())
        self.assertEqual("cheka",generator.next())
        self.assertRaises(StopIteration,lambda : generator.next() )
        
    def test_next_builtin(self):
        """
        to get next value from a generator, one method is to use 'generator.next()' member method
        another method is to use 'next(generator)' builtin function
        """
        def sample_generator():
            yield "cheka"
            yield 1
            
        gen = sample_generator()
        
        self.assertEqual("cheka",next(gen))
        self.assertEqual(1,next(gen))
        self.assertRaises(StopIteration,lambda : next(gen) )
        
    def test_duplex_generator(self):
        """
        not only can retrieve data from generator, but can inject data into generator
        """
        # ----------------- make duplex generator
        def _simple_duplex_generator(start = 0):
            count = start
            while True:
                injected = (yield count)
                if injected is not None:
                    count = injected
                else:
                    count += 1
                    
        # ----------------- make duplex generator
        generator = _simple_duplex_generator(4)
        self.assertEqual(4,generator.next())
        self.assertEqual(5,generator.next())
        
        self.assertEqual(10, generator.send(10) )
        self.assertEqual(11, generator.next())
        
        generator.close()
        self.assertRaises(StopIteration,lambda:  generator.next())
            
        
if __name__ == "__main__":
    unittest.main()